Skip to content

Fix crdt input buffer inconsistencies with agent prompts#13002

Merged
seemeroland merged 8 commits into
masterfrom
roland/fix-crdt-agent-prompt
Jun 25, 2026
Merged

Fix crdt input buffer inconsistencies with agent prompts#13002
seemeroland merged 8 commits into
masterfrom
roland/fix-crdt-agent-prompt

Conversation

@seemeroland

@seemeroland seemeroland commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Description

The input buffer ID is determined by the active block ID. When making a bunch of agent prompts, the active block stays the same, so we keep the same input buffer ID even though submitting agent prompts clears the input.

When a viewer submitted a prompt, they would see a frozen input UI (this created a temporary buffer for this frozen input) until the server sends back a "prompt in flight" ack. On this ack, the viewer would unfreeze and clear the input.

However, this clear was done by reinitializing the buffer, which replaces it completely and does not emit a CRDT operation. The sharer on receiving the prompt request would also call unfreeze and clear input, which does not emit a CRDT operation. As a result a new viewer joining would still see the input populated, because there was no CRDT input update to clear the buffer.

The takeaway is that changes to the input buffer must by synchronized through crdt operations. The viewer now only unfreezes the input on the "prompt in flight" ack. When the sharer executes the prompt, it will ultimately go through system_clear_buffer, which will emit a crdt operation, and all viewers will apply that. This alone fixes the issue, but the viewer who sent the prompt would see the input become unfrozen and then get cleared a moment later. To smooth out this UI, the viewer sending the prompt optimistically clears the input by displaying an ephemeral empty buffer. When a crdt operation comes in (the clear), we replace the ephemeral buffer with the real one with the crdt operation applied, so that new operations can be applied.

Alternative considered

Another way to fix the issue would have been to create a new buffer ID on every agent prompt submitted (similar to on every shell command submitted). But we don't have an existing mechanism to synchronize this buffer ID, which would be more complicated to add. Block IDs are automatically synchronized because they come from pty bytes that create the block.

Testing

  • I have manually tested my changes locally with ./script/run

Screenshots / Videos

https://www.loom.com/share/cf6461aacabb4075a4e804f11b96c876

Agent Mode

  • Warp Agent Mode - This PR was created via Warp's AI Agent Mode

@cla-bot cla-bot Bot added the cla-signed label Jun 24, 2026
@seemeroland seemeroland requested review from abhishekp106 and harryalbert and removed request for abhishekp106 June 24, 2026 18:26
@seemeroland seemeroland marked this pull request as ready for review June 24, 2026 18:56
@oz-for-oss

oz-for-oss Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

@seemeroland

I'm starting a first review of this pull request.

You can view the conversation on Warp.

I completed the review and no human review was requested for this pull request.

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

@oz-for-oss oz-for-oss Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overview

This PR changes shared-session agent prompt input handling so unfreezing no longer reinitializes the CRDT buffer, adds a display-only ephemeral empty buffer for optimistic clearing, and updates the relevant acknowledgement/failure paths and tests.

Concerns

  • The display-only ephemeral can become editable before the sharer's CRDT clear is applied, so the first user edit may operate on hidden stale prompt contents and emit CRDT operations for the wrong text.

Verdict

Found: 0 critical, 1 important, 0 suggestions

Request changes

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

// which would generate spurious CRDT delete ops for whatever the regular
// buffer currently contains (e.g. another viewer's concurrent edits).
self.buffer_and_display_map.deactivate_ephemeral_state();
None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [IMPORTANT] Discarding the display-only ephemeral here lets the first edit before the sharer's CRDT clear arrives run against the regular buffer that still contains the submitted prompt; backspace/delete or selection edits can emit CRDT ops against hidden prompt text, so keep the input non-editable until the clear arrives or materialize against an already-cleared regular buffer.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested viewer typing immediately after sending a query. It's fine - there's a brief moment the inputs are appended to the prompt, but the correct prompt is sent, and the inputs end up in the new buffer (and are consistent across partaicipants)

@harryalbert harryalbert left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great find

/// Exits the ephemeral loading state created by `set_buffer_text_ignoring_undo`
/// without touching the CRDT buffer or emitting any `UpdatePeers` operations.
/// The editor switches back to displaying the regular collaborative buffer.
pub fn exit_ephemeral_loading_state(&mut self, ctx: &mut ViewContext<Self>) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC, we don't really have an 'loading' state anymore right (or at least a visual loading state)? I guess the ephemeral buffer is the loading state?

@seemeroland seemeroland Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea we do still have the UI of making the input gray and adding a spinner between viewer submitting prompt and receiving the ack - that's the ephemeral buffer for loading state

/// (exited by a non-ephemeral edit), its content is discarded rather than applied
/// to the regular buffer, preventing spurious CRDT operations.
fn activate_display_only_ephemeral_state(&mut self, ctx: &mut ModelContext<EditorModel>) {
self.activate_new_ephemeral_state(ctx);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what happens if a viewer submits a prompt while in the ephemeral state?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried and wasn't able to

Comment thread app/src/terminal/input.rs Outdated
/// to retry.
pub fn unfreeze_agent_input(
&mut self,
optimistically_show_empty: bool,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you maybe add a comment on this field or rename it? I read the comment above, but I'm still not entirely grokking what this field is for

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

Base automatically changed from roland/buffer-input-updates-offline to master June 25, 2026 19:01
@seemeroland seemeroland enabled auto-merge (squash) June 25, 2026 20:53
@seemeroland seemeroland merged commit 45e8223 into master Jun 25, 2026
25 checks passed
@seemeroland seemeroland deleted the roland/fix-crdt-agent-prompt branch June 25, 2026 21:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants